;; Happy hacking, demo - Emacs ♥ you!


;; hello world
;; ----------------------------------------
;; 在Minibuffer输出`hello world'
;; message 函数它的作用是在 Minibuffer 里显示一个字符串
;; 它的返回值是 "hello world" 字符串
(message "hello world")


;; 基础知识
;; ----------------------------------------
;; * 函数和变量

;; 定义函数
;; (defun function-name (arguments-list)
;;   "document string"
;;   body)
;; 例:
(defun hello-world (name)
  "Say hello to user whose name is NAME."
  (message "Hello, %s" name))
;; 文档字符串可以省略, 但建议（除了最简单，不作为接口的）都加上文档字符串
;; 当光标处于一个函数名上时, 可以用`C-h f'查看函数的文档
;; 如果你的函数是在文件中定义的, 这个文档里还会给出一个链接能跳到定义的地方

;; 运行函数
;; (function-name arguments-list)
(hello-world "Emacser") ; => "Hello, Emacser"
;; 每个函数都有返回值, 一般是函数定义里的最后一个表达式的值

;; 定义变量
(setq foo "I'm foo") ; => "I'm foo"
(message foo) ; => "I'm foo"
;; elisp变量无需声明, 可以用setq直接对一个变量赋值
;; 同函数一样, 可以用`C-h v'查看一个变量的文档

;; 声明变量
;; 特殊表达式(special form) defvar 可以用来声明一个变量
;; (defvar variable-name value
;;   "document string")
;; 与setq不同的是:
;;   1. 如果变量在声明之前已经有值, 则不会重新赋值
;;   2. defvar可以为变量提供文档字符串
(defvar foo "Did I have a value?"
  "A demo variable") ; => foo
(message foo) ; => "I'm foo"
(defvar bar "I'm bar"
  "A demo variable named \"bar\"") ; => bar
(message bar) ; => "I'm bar"

;; 注意: 由于 elisp 中函数是全局的，变量也很容易成为全局变量（因为全局变量和局部变量的赋值都是使用 setq 函数），名字不互相冲突是很关键的。
;; 所以除了为你的函数和变量选择一个合适的前缀之外，用 C-h f 和 C-h v 查看一下函数名和变量名有没有已经被使用过是很关键的。

;; * 局部作用域的变量
;; 如果没有局部作用域的变量，都使用全局变量，函数会相当难写。
;; elisp 里可以用 let 和 let* 进行局部变量的绑定。
;; let 使用的形式是：
;; (let (bindings) body)
;; bindings 可以是 `(var-name value)' 这样对var-name赋初始值的形式, 也可以仅用`var-name'声明一个初始值为nil的变量
(defun circle-area (radix)
  (let ((pi 3.1415926)
        area)
    (setq area (* pi radix radix))
    (message "半径为 %.2f 的圆面积是 %.2f" radix area)))
(circle-area 3)
;; 因为 area 和 pi 两个变量使用let声明在函数内部, 使用`C-h v'无法找到这两个变量

;; let* 和 let 的使用形式完全相同, 唯一的区别在于 let* 在声明中就能使用前面声明的变量
(defun circle-area (radix)
  (let* ((pi 3.1415926)
         (area (* pi radix radix)))
    (message "半径为 %.2f 的圆面积为 %.2f" radix area)))
(circle-area 3)

;; * lambda 表达式
;; 相当于其他语言中的匿名函数, 形式和 defun 是完全一样的
;; (lambda (arguments-list)
;; "document string"
;; body)
;; 通过 funcall 调用
(funcall (lambda (name)
           (message "Hello, %s!" name)) "Emacser")
;; 也可以把 lambda 表达式赋值给一个变量, 然后用 funcall 调用
(setq foo (lambda (name)
            (message "Hello, %s!" name)))
(funcall foo "Emacser") ; => "Hello, Emacser!"
;; lambda 表达式最常用的是作为参数传递给其他函数, 比如 mapc

;; * 控制结构
;; 顺序执行
;; 一般来说程序都是按表达式顺序依次执行的
;; 这在 defun 等特殊环境中是自动进行的, 但在一般情况下都不是这样的
;; 比如你无法用 eval-last-sexp 同时执行两个表达式
;; 在 if 表达式中的条件为真时执行的部分也只能运行一个表达式
;; 这时就需要用 progn 这个特殊表达式 (progn A B C ...)
;; 它的作用就是让表达式A, B, C顺序执行
(progn
  (setq foo 3)
  (message "Square of %d is %d" foo (* foo foo)))

;; 条件判断
;; elisp 有两个最基本的条件判断表达式 if 和 cond
;; (if condition
;;     then
;;   else)
;; (cond (case1 do-when-case1)
;;       (case2 do-when-case2)
;;       ...
;;       (t do-when-none-meet))
(defun my-max (a b)
  (if (> a b)
      a b))
(my-max 3 4) ; => 4

(defun fib (n)
  (cond ((= n 0) 0)
        ((= n 1) 1)
        (t (+ (fib (- n 1))
              (fib (- n 2))))))
(fib 10) ; => 55
;; 还有两个宏 when 和 unless
;; 使用这两个宏的好处是能增强代码的可读性
;; when 能省去 if 里的 progn 结构, unless 省去条件为真字句需要的 nil 表达式

;; 循环
;; (while condition
;;   body)
(defun factorial (n)
  (let ((res 1))
    (while (> n 1)
      (setq res (* res n)
            n (- n 1)))
    res))
(factorial 10) ; => 3628800

;; * 逻辑运算
;; and, or, not (and 和 or 同样具有短路性质)
;; 很多人喜欢在表达式短时，用 and 代替 when，or 代替 unless。
;; 当然这时一般不关心它们的返回值，而是在于表达式其它子句的副作用。
;; 比如 or 经常用于设置函数的缺省值，而 and 常用于参数检查
(defun hello-world (&optional name)
  (or name (setq name "Emacser"))
  (message "Hello, %s" name))
(hello-world) ; => "Hello, Emacser"
(hello-world "Ye") ; => "Hello, Ye"

(defun square-number-p (n)
  (and (>= n 0)
       (= (/ n (sqrt n)) (sqrt n))))
(square-number-p -1) ; => nil
(square-number-p 25) ; => t

;; * 函数列表
;; (defun NAME ARGLIST [DOCSTRING] BODY...)
;; (defvar SYMBOL &optional INITVALUE DOCSTRING)
;; (setq SYM VAL SYM VAL ...)
;; (let VARLIST BODY...)
;; (let* VARLIST BODY...)
;; (lambda ARGS [DOCSTRING] [INTERACTION] BODY)
;; (progn BODY ...)
;; (if COND THEN ELSE...)
;; (cond CLAUSES...)
;; (when COND BODY ...)
;; (unless COND BODY ...)
;; (or CONDITIONS ...)
;; (and CONDITIONS ...)
;; (not OBJECT)

;; 基本数据类型之一 —— 数字
;; --------------------------------------
;; elisp 里的对象都是有类型的，而且每一个对象它们知道自己是什么类型。
;; 你得到一个变量名之后可以用一系列检测方法来测试这个变量是什么类型（好像没有什么方法来让它说出自己是什么类型的）。
;; 内建的 emacs 数据类型称为 primitive types，
;;   包括整数、浮点数、cons、符号(symbol)、字符串、向量(vector)、散列表(hash-table)、
;;   subr（内建函数，比如 cons, if, and 之类）、byte-code function，和其它特殊类型，例如缓冲区（buffer）。
;;
;; 在开始前有必要先了解一下读入语法和输出形式。
;; 所谓读入语法是让 elisp 解释器明白输入字符所代表的对象，你不可能让 elisp 读入 .#@!? 这样奇怪的东西还能好好工作吧（perl好像经常要受这样的折磨:)）。
;; 简单的来说，一种数据类型有（也可能没有，比如散列表）对应的规则来让解释器产生这种数据类型，比如 123 产生整数 123，(a . b) 产生一个 cons。
;; 所谓输出形式是解释器用产生一个字符串来表示一个数据对象。
;; 比如整数 123 的输出形式就是 123，cons cell (a . b) 的输出形式是 (a . b)。
;; 与读入语法不同的是，数据对象都有输出形式。比如散列表的输出可能是这样的
;; #<hash-table 'eql nil 0/65 0xa7344c8>
;; 通常一个对象的数据对象的输出形式和它的读入形式都是相同的
;; 现在就先从简单的数据类型──数字开始吧。
;; emacs 的数字分为整数和浮点数（和 C 比没有双精度数 double）。
;; 1， 1.，+1, -1, 536870913, 0, -0 这些都是整数。
;; 整数的范围是和机器是有关的，一般来最小范围是在 -268435456 to 268435455（29位，-2**28 ~ 2**28-1）。
;; 可以从 most-positive-fixnum 和 most-negative-fixnum 两个变量得到整数的范围。
#b101100 ; => 44 二进制
#o54     ; => 44 八进制
#x2c     ; => 44 十六进制
;; 最神奇的是可以用2到36之间的任意一个数作为进制基数
#24r1k   ; => 44 二十四进制
;; 之所以最大是36, 是因为只有0-9和a-z共36个字符来表示数字
;; 1500.0, 15e2, 15.0e2, 1.5e3, 和 .15e4 都可以用来表示一个浮点数 1500.。
;; 遵循 IEEE 标准，elisp 也有一个特殊类型的值称为 NaN (not-a-number)。你可以用 (/ 0.0 0.0) 产生这个数。


;; * 测试函数
;; integerp 是否为整型
;; floatp 是否为浮点型
;; numberp 是否为数字类型
;; zerop 是否为零
;; wholenump 是否为非负整数
;; 注: elisp 测试函数一般都是用p来结尾, p 是 predicate 的首字母
;;  如果函数名是一个单词, 通常只在这个单词后加一个p, 如果是多个单词, 一般是加 -p
(integerp 1.) ; => t 是否为整型
(integerp 1.0) ; => nil
(floatp 1.) ; => nil 是否为浮点型
(floatp 1.0) ; => t
(floatp -0.0e+NaN) ; => t
(numberp 1) ; => t 是否为数字类型
(numberp (/ 0.0 0.0)) ; => t

;; * 数的比较
;; 常用的比较操作符号是我们在其它言中都很熟悉的，比如 <, >, >=, <=，
;; 不一样的是，由于赋值是使用 set 函数，所以 = 不再是一个赋值运算符了，而是测试数字相等符号。
;; 和其它语言类似，对于浮点数的相等测试都是不可靠的
(setq foo (- (+ 1.0 1.0e-3) 1.0)) ; => 0.0009999999999998899
(setq bar 1.0e-3) ; => 0.001
(= foo bar) ; => nil
;; 所以比较两个浮点数时, 一般要在一定误差内进行比较
(defvar fuzz-factor 1.0e-6)
(defun approx-equal (x y)
  (or (and (= x 0) (= y 0))
      (< (/ (abs (- x y))
            (max (abs x) (abs y)))
         fuzz-factor)))
(approx-equal foo bar) ; => t
;; 全等于测试函数 eql, 即不仅测试数字的值是否相等, 还测试数字类型是否一致
(= 1.0 1) ; => t
(eql 1.0 1) ; => nil
;; elisp 没有 +=, -=, /=, *= 这样的命令式语言里常见符号，如果你想实现类似功能的语句，只能用赋值函数 setq 来实现了。
;; 注: /= 符号被用来作为不等于的测试了。

;; * 数的转换
;; 整数转换为浮点数使用 float 函数, 浮点数转换为整数有以下几种方式
;; truncate 直接舍去取整
;; floor 向下取整
;; ceiling 向上取整
;; round 四舍五入取整
(truncate 1.9) ; => 1
(truncate -1.9) ; => -1
(floor 1.9) ; => 1
(floor -1.9) ; => -2
(ceiling 1.9) ; => 2
(ceiling -1.9) ; => -1
(round 1.9) ; => 2
(round -1.9) ; => -2
;; 可以直接看 info。按键顺序是 C-h i m elisp RET m Numeric Conversions RET

;; * 数的运算
;; 四则运算就是 + - * /。
;; 和 C 语言类似，如果参数都是整数，作除法时要记住 (/ 5 6) 是会等于 0 的。
;; 如果参数中有浮点数，整数会自动转换成浮点数进行运算，所以 (/ 5 6.0) 的值才会是 5/6。
;; 没有 ++ 和 -- 操作了，类似的两个函数是 1+ 和 1-。可以用 setq 赋值来代替 ++ 和 --：
(setq foo 10) ; => 10
(setq foo (1+ foo)) ; => 11
(setq foo (1- foo)) ; => 10
;; 注：可能有人看过有 incf 和 decf 两个实现 ++ 和 -- 操作。这两个宏是可以用的。
;; 这两个宏是 Common Lisp 里的，emacs 有模拟的 Common Lisp 的库 cl。但是最好不要使用这个库。
;; 如果要在 elisp 包中使用这两个宏，只要在文件头写上：
(eval-when-compile (require 'cl))
;; 由于 incf 和 decf 是两个宏，所以这样写不会在运行里导入 cl 库。总之, 不推荐使用
(abs -1.0) ; => 1.0 取绝对值
(% 10 3) ; => 1 取模, 第一个参数必须是整数
(mod 10.0 3) ; => 1.0 取模, 第一个参数可以是浮点数
(sqrt 25) ; => 5.0 开方函数
;; 三角函数 sin, cos, tan, asin, acos, atan
;; exp 是以 e 为底的指数运算，
;; expt 可以指定底数的指数运算。
;; log 默认底数是 e，但是也可以指定底数。
;; log10 就是 (log x 10)。
;; logb 是以 2 为底数运算，但是返回的是一个整数。这个函数是用来计算数的位。
(random) ; 产生随机数。
;; 可以用 (random t) 来产生一个新种子。
;; 虽然 emacs 每次启动后调用 random 总是产生相同的随机数，但是运行过程中，你不知道调用了多少次，所以使用时还是不需要再调用一次 (random t) 来产生新的种子。
(lsh 5 1) ; => 10 逻辑移位操作, 第二个参数为正整数时左移, 为负整数时右移
(lsh 6 -1) ; => 3
(ash 6 -1) ; => 3 算术移位操作, 和逻辑移位比起来，算术移位是保持符号的。 对于负数而言，这两个操作的区别是明显的
(logand 3 5) ; => 1 逻辑与 #b11 & #b101 = #b1
(logior 3 5) ; => 7 逻辑或 #b11 | #b101 = #b111
(logxor 3 5) ; => 6 逻辑异或 #b11 ^ #b101 = #b110
(lognot 1) ; => -2 逻辑取反

;; * 函数列表
;; ;; 测试函数
;; (integerp OBJECT)
;; (floatp OBJECT)
;; (numberp OBJECT)
;; (zerop NUMBER)
;; (wholenump OBJECT)
;; ;; 比较函数
;; (> NUM1 NUM2)
;; (< NUM1 NUM2)
;; (>= NUM1 NUM2)
;; (<= NUM1 NUM2)
;; (= NUM1 NUM2)
;; (eql OBJ1 OBJ2)
;; (/= NUM1 NUM2)
;; ;; 转换函数
;; (float ARG)
;; (truncate ARG &optional DIVISOR)
;; (floor ARG &optional DIVISOR)
;; (ceiling ARG &optional DIVISOR)
;; (round ARG &optional DIVISOR)
;; ;; 运算
;; (+ &rest NUMBERS-OR-MARKERS)
;; (- &optional NUMBER-OR-MARKER &rest MORE-NUMBERS-OR-MARKERS)
;; (* &rest NUMBERS-OR-MARKERS)
;; (/ DIVIDEND DIVISOR &rest DIVISORS)
;; (1+ NUMBER)
;; (1- NUMBER)
;; (abs ARG)
;; (% X Y)
;; (mod X Y)
;; (sin ARG)
;; (cos ARG)
;; (tan ARG)
;; (asin ARG)
;; (acos ARG)
;; (atan Y &optional X)
;; (sqrt ARG)
;; (exp ARG)
;; (expt ARG1 ARG2)
;; (log ARG &optional BASE)
;; (log10 ARG)
;; (logb ARG)
;; ;; 随机数
;; (random &optional N)

;; * 变量列表
most-positive-fixnum ; 最大整型数
most-negative-fixnum ; 最小整型数

;; 基本数据类型之二 —— 字符和字符串
;; -----------------------------------
;; 在 emacs 里字符串是有序的字符数组。和 c 语言的字符串数组不同，emacs 的字符串可以容纳任何字符，包括 \0
(setq foo "abc\000abc") ; => "abc^@abc"
;; 关于字符串有很多高级的属性，例如字符串的表示有单字节和多字节类型，字符串可以有文本属性（text property）等等。
;; 但是对于刚接触字符串，还是先学一些基本操作吧。
;; 首先构成字符串的字符其实就是一个整数。
;; 一个字符 'A' 就是一个整数 65。但是目前字符串中的字符被限制在 0-524287 之间。
;; 字符的读入语法是在字符前加上一个问号，比如 ?A 代表字符 'A'。
?A ; => 65
?a ; => 97
;; 对于标点来说，也可以用同样的语法，但是最好在前面加上转义字符 \，因为有些标点会有岐义，比如 ?\(。
;; \ 必须用 ?\\ 表示。控制字符，退格、制表符，换行符，垂直制表符，换页符，空格，回车，删除和 escape
;; 表示为 ?\a, ?\b, ?\t, ?\n, ?\v, ?\f, ?\s, ?\r, ?\d, 和 ?\e。
;; 对于没有特殊意义的字符，加上转义字符 \ 是没有副作用的，比如 ?\+ 和 ?+ 是完全一样的。所以标点还是都用转义字符来表示吧。
?\a ; => 7                 ; control-g, `C-g'
?\b ; => 8                 ; backspace, <BS>, `C-h'
?\t ; => 9                 ; tab, <TAB>, `C-i'
?\n ; => 10                ; newline, `C-j'
?\v ; => 11                ; vertical tab, `C-k'
?\f ; => 12                ; formfeed character, `C-l'
?\r ; => 13                ; carriage return, <RET>, `C-m'
?\e ; => 27                ; escape character, <ESC>, `C-['
?\s ; => 32                ; space character, <SPC>
?\\ ; => 92                ; backslash character, `\'
?\d ; => 127               ; delete character, <DEL>
;; 控制字符可以有多种表示方式，比如 C-i，这些都是对的：
;; ?\^I  ?\^i  ?\C-I  ?\C-i  他们都对应数字9
;; meta 字符是用 修饰键（通常就是 alt 键）输入的字符。之所以称为修饰键，
;; 是因为这样输入的字符就是在其修饰字符的第 27 位由 0 变成 1 而成，也就是如下操作：
(logior (lsh 1 27) ?A) ; => 134217793
?\M-A ; => 134217793
;; 你可以用 \M- 代表 meta 键，加上修饰的字符就是新生成的字符。
;; 比如：?\M-A, ?\M-\C-b. 后面这个也可以写成 ?\C-\M-b。
;; 如果你还记得前面说过字符串里的字符不能超过 524287 的话，这就可以看出字符串是不能放下一个 meta 字符的。
;; 所以按键序列在这时只能用 vector 来储存。
;; 其它的修饰键也是类似的。emacs 用 2**25 位来表示 shift 键，2**24 对应 hyper，2**23 对应 super，2**22 对应 alt。

;; * 测试函数
(stringp "abc") ; => t
(stringp 123) ; => nil
(stringp "123") ; => t
(string-or-null-p nil) ; => t ; 当对象是一个字符或 nil 时返回 t
(char-or-string-p ?A) ; => t ; 是否是字符串或者字符类型
;; 比较头疼的是 emacs 没有测试字符串是否为空的函数。
;; 可以用如下函数，使用前要测试字符串是否为 nil
(defun string-empty-p (str)
  (not (string< "" str)))
(string-empty-p "") ; => t

;; * 构造函数
;; 产生一个字符串可以用 make-string。这样生成的字符串包含的字符都是一样的。
;; 要生成不同的字符串可以用 string 函数。
(make-string 5 ?x) ; => "xxxxx"
(string ?a ?b ?c) ; => "abc"
(substring "0123456789" 3) ; => "3456789"
(substring "0123456789" 3 5) ; => "34"
(substring "0123456789" -3 -1) ; => "78"
(concat "abc" "def" "g") ; => "abcdefg"

;; * 字符串比较
(char-equal ?a ?b) ; => nil 比较两个字符是否相等
(char-equal ?a ?a) ; => t
(char-equal ?a 97) ; => t
(char-equal ?a ?A) ; => t 如果 case-fold-search 变量是 t 时, 该函数的字符比较是忽略大小写的
(string-equal "abc" "abc") ; => t 字符串比较
(string= "abc" "abc") ; => t 字符串比较, 函数 string-equal 的别名
(string< "abc" "abd") ; => t 按字典序比较两个字符串, 函数 string-less 的别名。不存在 string> 函数
;; 注: 空字符串小于所有其他字符串
;; 前面 string-empty-p就是用的这个特性. 当然直接用 length 检测字符串长度也可以.

;; * 转换函数
(char-to-string ?a) ; => "a" 字符转换为字符串
(string-to-char "abc") ; => 97 字符串转换为字符，仅返回第一个字符
(string-to-list "abc") ; => (97 98 99) 字符串转换为字符列表
(string-to-number "256") ; => 256 字符串转数字
(number-to-string 256) ; => "256" 数字转字符串
(format "%#o" 256) ; => "0400" 数字转八进制表示的字符串
(format "%#x" 256) ; => "0x100" 数字转十六进制表示的字符串
;; 如果要输出二进制表示的字符串, 可以使用calculator库
(defun number-to-bin-string (number)
  (require 'calculator)
  (let ((calculator-output-radix 'bin)
        (calculator-radix-grouping-mode nil))
    (calculator-number-to-string number)))
(number-to-bin-string 256) ; => "100000000"
(concat '(?a ?b ?c ?d ?e)) ; => "abcde" concat可以把一个字符构成的列表或者向量转换成字符串
(concat [?a ?b ?c ?d ?e]) ; => "abcde"
(vconcat "abcde") ; => [97 98 99 199 101] vconcat可以把一个字符串转换成一个向量
(append "abcde" nil) ; => (97 98 99 100 101) append可以把一个字符串转换成一个列表
(downcase "The cat in the hat") ; => "the cat in the hat" ; 转小写
(downcase ?X) ; => 120
(upcase "The cat in the hat") ; => "THE CAT IN THE HAT" ; 转大写
(upcase ?x) ; => 88
(capitalize "The CAT in the hat") ; => "The Cat In The Hat" ; 单词首字母大写, 剩余字符转小写
(upcase-initials "The CAT in the hAt") ; => "The CAT In The HAt" ; 单词首字母大写, 且保持剩余字符大小写不变

;; * 格式化字符串
;; format 类似于 C 语言里的 printf 可以实现对象的字符串化。
;; 数字的格式化和 printf 的参数差不多，值得一提的是 "%S" 这个格式化形式，它可以把对象的输出形式转换成字符串，这在调试时是很有用的。

;; * 查找和替换
;; 字符串查找的核心函数是 string-match
;; 这个函数可以从指定的位置对字符串进行正则表达式匹配, 如果匹配成功, 则返回匹配的起点
(string-match "34" "01234567890123456789") ; => 3
(string-match "34" "01234567890123456789" 10) ; => 13
;; 注: string-match 的参数是一个regexp, emacs 没有内建的查找字串的函数
;; 如果你想把 string-match 作为一个查找字串的函数, 可以先用 regexp-quote 函数处理一下字串
(string-match "2*" "232*3=696") ; => 0 ; * 被视为正则中的通配符
(string-match (regexp-quote "2*") "232*3=696") ; => 2
;; 事实上, string-match 不只是查找字符串, 它更重要的功能是捕获匹配的字符串
;; string-match 在查找的同事，还会记录下每个要捕捉的字符串的位置
;; 这个位置可以在匹配后用 match-data, match-beginning 和 match-end 等函数来获得
(progn
  (string-match "3\\(4\\)" "01234567890123456789")
  (match-data)) ; => (3 5 4 5)
;; 最后返回值解读: 正则表达式捕捉的字符串按括号的顺序对应一个序号，
;; 整个模式对应序号 0，第一个括号对应序号 1，第二个括号对应序号 2，以此类推。
;; 所以 "3\(4\)" 这个正则表达式中有序号 0 和 1，
;; 最后 match-data 返回的一系列数字对应的分别是要捕捉字符串的起点和终点位置，
;; 也就是说子串 "34" 起点从位置 3 开始，到位置 5 结束，而捕捉的字符串 "4" 的起点是从 4 开始，到 5 结束。
;; 这些位置可以用 match-beginning 和 match-end 函数用对应的序号得到。
;; 要注意的是，起点位置是捕捉字符串的第一个字符的位置，而终点位置不是捕捉的字符串最后一个字符的位置，而是下一个字符的位置。
;; 这个性质对于循环是很方便的。比如要查找上面这个字符串中所有 34 出现的位置：
(let ((start 0))
  (while (string-match "34" "01234567890123456789" start)
    (princ (format "find at %d\n" (match-beginning 0)))
    (setq start (match-end 0))))
;; 替换使用的函数是 replace-match。这个函数既可以用于字符串的替换，也可以用于缓冲区的文本替换。
;; 对于字符串的替换，replace-match 只是按给定的序号把字符串中的那一部分用提供的字符串替换了而已：
(let ((str "01234567890123456789"))
  (string-match "34" str)
  (princ (replace-match "x" nil nil str 0))
  (princ "\n")
  (princ str))
;; 注: replace-match返回的字符串是替换后的新字符串, 原字符串并没有被改变
;; 如果你想挑战一下，想想怎样把上面这个字符串中所有的 34 都替换掉？
;; 如果想就使用同一个字符串来存储，可能对于固定的字符串，这个还容易一些，
;; 如果不是的话，就要花一些脑筋了，因为替换之后，新的字符串下一个搜索起点的位置就不能用 (match-end 0) 给出来的位置了，
;; 而是要扣除替换的字符串和被替换的字符串长度的差值。
;; emacs 对字符串的替换有一个函数 replace-regexp-in-string。这个函数的实现方法是把每次匹配部分之前的子串收集起来，最后再把所有字符串连接起来。
;; 单字符的替换有 subst-char-in-string 函数。但是 emacs 没有类似 perl函数或者程序 tr 那样进行字符替换的函数。只能自己建表进行循环操作了。

;; * 函数列表
;; ;; 测试函数
;; (stringp OBJECT)
;; (string-or-null-p OBJECT)
;; (char-or-string-p OBJECT)
;; ;; 构建函数
;; (make-string LENGTH INIT)
;; (string &rest CHARACTERS)
;; (substring STRING FROM &optional TO)
;; (concat &rest SEQUENCES)
;; ;; 比较函数
;; (char-equal C1 C2)
;; (string= S1 S2)
;; (string-equal S1 S2)
;; (string< S1 S2)
;; ;; 转换函数
;; (char-to-string CHAR)
;; (string-to-char STRING)
;; (number-to-string NUMBER)
;; (string-to-number STRING &optional BASE)
;; (downcase OBJ)
;; (upcase OBJ)
;; (capitalize OBJ)
;; (upcase-initials OBJ)
;; (format STRING &rest OBJECTS)
;; ;; 查找与替换
;; (string-match REGEXP STRING &optional START)
;; (replace-match NEWTEXT &optional FIXEDCASE LITERAL STRING SUBEXP)
;; (replace-regexp-in-string REGEXP REP STRING &optional FIXEDCASE LITERAL SUBEXP START)
;; (subst-char-in-string FROMCHAR TOCHAR STRING &optional INPLACE)

;; 基本数据类型之三 —— CONS CELL 和列表
;; ---------------------------------------------
;; 如果从概念上来说，cons cell 其实非常简单的，就是两个有顺序的元素。
;; 第一个叫 CAR，第二个就 CDR。CAR 和 CDR 名字来自于 Lisp。它最初在IBM 704机器上的实现。
;; 在这种机器有一种取址模式，使人可以访问一个存储地址中的“地址（address）”部分和“减量（decrement）”部分。
;; CAR 指令用于取出地址部分，表示(Contents of Address part of Register)，
;; CDR 指令用于取出地址的减量部分(Contents of the Decrement part of Register)。
;; cons cell 也就是 construction of cells。
;; car 函数用于取得 cons cell 的 CAR 部分，cdr 取得cons cell 的 CDR 部分。
;; cons cell 如此简单，但是它却能衍生出许多高级的数据结构，比如链表，树，关联表等等。

;; cons cell 的读入语法是用 . 分开两个部分，比如：
'(1 . 2)      ; => (1 . 2)
'(?a . 1)     ; => (97 . 1)
'(1 . "a")    ; => (1 . "a")
'(1 . nil)    ; => (1)
'(nil . nil)  ; => (nil)

;; 注意到前面的表达式中都有一个 ' 号，这是什么意思呢？其实理解了 eval-last-sexp 的作用就能明白了。
;; eval-last-sexp 其实包含了两个步骤，一是读入前一个 S-表达式，二是对读入的 S-表达式求值。
;; 这样如果读入的 S-表达式是一个 cons cell 的话，求值时会把这个 cons cell 的第一个元素作为一个函数来调用。
;; 而事实上，前面这些例子的第一个元素都不是一个函数，这样就会产生一个错误 invalid-function。
;; 之所以前面没有遇到这个问题，那是因为前面数字和字符串是一类特殊的 S-表达式，它们求值后和求值前是不变，称为自求值表达式（self-evaluating form）。
;; ' 号其实是一个特殊的函数 quote，它的作用是将它的参数返回而不作求值。
;; '(1 . 2) 等价于 (quote (1 . 2))。
;; 为了证明 cons cell 的读入语法确实就是它的输出形式，可以看下面这个语句：
(read "(1 . 2)")    ; => (1 . 2)
;; 列表包括了 cons cell。但是列表中有一个特殊的元素——空表nil。
nil    ; => nil
'()    ; => nil
;; 空表不是一个 cons cell，因为它没有 CAR 和 CDR 两个部分，事实上空表里没有任何内容。
;; 但是为了编程的方便，可以认为 nil 的 CAR 和 CDR 都是 nil：
(car nil)  ; => nil
(cdr nil)  ; => nil

;; 按列表最后一个 cons cell 的 CDR 部分的类型分，可以把列表分为三类。
;; 如果它是 nil 的话，这个列表也称为“真列表”(true list)。
;; 如果既不是 nil 也不是一个 cons cell，则这个列表称为“点列表”(dotted list)。
;; 还有一种可能，它指向列表中之前的一个 cons cell，则称为环形列表(circular list)。
;; 这里分别给出一个例子：
'(1 2 3)               ; => (1 2 3)
'(1 2 . 3)             ; => (1 2 . 3)
'(1 . #1= (2 3 . #1#)) ; => (1 2 3 . #1)
;; 从这个例子可以看出前两种列表的读入语法和输出形式都是相同的，而环形列表的读入语法是很古怪的，输出形式不能作为环形列表的读入形式。

;; 如果把真列表最后一个 cons cell 的 nil 省略不写，
;; 也就是 (1 . nil) 简写成 (1)，把 ( obj1 . ( obj2 . list)) 简写成 (obj1 obj2 . list)，
;; 那么列表最后可以写成一个用括号括起的元素列表：
'(1 . (2 . (3 . nil)))  ; => (1 2 3)

;; 尽管这样写是清爽多了，但是，我觉得看一个列表时还是在脑子里反映的前面的形式，
;; 这样在和复杂的 cons cell 打交道时就不会搞不清楚这个 cons cell 的 CDR 是一个列表呢，还是一个元素或者是嵌套的列表。

















;; 零碎知识点
(org-list-to-subtree '("* [[file:/home/demo/Code/demokn/blog/src/posts/test/index.org][Test]]\n:PROPERTIES:\n:RSS_PERMALINK: test/index.html\n:PUBDATE:  2020-02-17\n:END:\n#+TITLE: Test\n#+DATE: <2020-02-17 Mon>\n\nThis is a test.\n") nil (list :istart "" :icount ""))

(org-list-to-subtree '(unordered ("* Test2\n:PROPERTIES:\n:RSS_PERMALINK: test2/index.html\n:PUBDATE:  2020-02-17\n:
END:\n#+TITLE: Test2\n\n Test2 content.\n") ("* Test1\n:PROPERTIES:\n:RSS_PERMALINK: test1/index.html\n:PUBDATE:  2020-02-17\n:END:\n#+TITLE: Test1\n\n T
est1 content.\n")) nil '(:istart "" :icount ""))


